
/* 
 * TagFolder
 *      Tagging everything
 * 
 * tagfolder.c
 *      the basic tag accessing library for tagfolder
 *      created by chuanshenglu@gmail.com
 */

#include "tagfolder.h"
#include "tf_log.h"
#include "tf_mem.h"
#include <sqlite3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if defined( __cplusplus)
extern "C"{
#endif

#if defined( DEFAULT_SQL_STMT_BUF_SIZE)
#undef DEFAULT_SQL_STMT_SIZE
#endif
#define DEFAULT_SQL_STMT_BUF_SIZE 2048

#if defined( DEFAULT_PATH_BUF_SIZE)
#undef DEFAULT_PATH_BUF_SIZE
#endif
#define DEFAULT_PATH_BUF_SIZE 1024

#define DEFAULT_CONFIG_FILE "/etc/tagfolder.conf"
#define DEFAULT_DB_FILE "tf.db"

/**
 * Name of database file;
 */
char *dbfile_name=NULL;

static int tagfolder_intialized=0;

static int add_file_record(const char* filename);
void cleanup();

/**
 * Flag is a pointer to int variable, set to 1 if exist;
 * set to 0 if not exist;
 */
static int tf_callback_if_result_set_empty( void *flag, int argc, char **argv, char** col_name){
    ENTER_FUNC;
    int ret=0;
    if( argc>0 && argv!=NULL && argv[0]!=NULL && flag!=NULL){
        *((int*)flag) = 1;
    }else{
        *((int*)flag) = 0;
    }
    LEAVE_FUNC;
    return ret;
}

static int tf_callback_append_string_list( void* taglist, int argc, char** argv, char** col){
    ENTER_FUNC;
    string_list* slist=( string_list*) taglist;
    push_back_string_list( slist, argv[0]);
    LEAVE_FUNC;
    return 0;
}

/**
 * Initialize function for tagfolder library
 * parameters:
 * return: 0=>OK, !0=>Error;
 */
int tf_init( int argc, char** argv){
    ENTER_FUNC;
    const char* config_file = DEFAULT_CONFIG_FILE;
    FILE *cfg=NULL;
    static char buf[DEFAULT_PATH_BUF_SIZE];
    char *dbfile=NULL;

    if( NULL != ( cfg = fopen(config_file, "rt" ) ) ){
        LEAVE_FUNC;
        while( EOF != fscanf( cfg, "%s", buf)){
            if( NULL!=(dbfile=strstr( buf, "database="))){
                dbfile+=9;
                dbfile_name=(char*) tf_malloc( strlen(dbfile)+1);
                strcpy( dbfile_name, dbfile);
            }
        }
        fclose( cfg);
    }

    if( dbfile_name == NULL){
        dbfile_name = (char*)malloc( strlen( DEFAULT_DB_FILE)+1);
        strcpy( dbfile_name, DEFAULT_DB_FILE);
    }
    INFO_LOG(dbfile_name);

    tf_log_init();
    LEAVE_FUNC;
}

/**
 * Destroy the tagfolder library at exit time;
 */
int tf_destroy( void ){
    ENTER_FUNC;
    if( dbfile_name!=NULL){
        tf_free( dbfile_name);
    }
    tf_log_destroy();
    LEAVE_FUNC;
}

/**
 *  Private function to open configured sqlite database;
 *  return: pointer to opened DB;
 *          NULL if failed to open;
 */
static sqlite3* open_db(){
    ENTER_FUNC;

    sqlite3* database=NULL;
    if( dbfile_name!=NULL){
        int ret=sqlite3_open( dbfile_name, &database);
        if( ret!=SQLITE_OK || database==NULL){
            ERR_LOG( "fail to open database file");
        }
    }

    LEAVE_FUNC;
    return database;
}

static int close_db( sqlite3* db){
    ENTER_FUNC;
    int ret=0;
    if( db!=NULL){
        ret=sqlite3_close(db);
    }
    LEAVE_FUNC;
    return ret;
}

/**
 * To detect is target tag exists;
 * parameter: tag=> name of tag;
 * return:  None-zero => exists
 *          zero => does not exist
 */
int if_tag_defined( const char* tag){
    ENTER_FUNC;

    if( tag==NULL || strlen(tag)==0){
        LEAVE_FUNC;
        return 0; //ret false, not means OK
    }

    sqlite3* db=open_db();
    char* err_msg=NULL;
    static char sql_stmt[DEFAULT_SQL_STMT_BUF_SIZE];

    strcpy( sql_stmt, "SELECT id FROM tag WHERE name=\"");
    strcat( sql_stmt, tag);
    strcat( sql_stmt, "\"");

    if( db!=NULL){
        int __if_tag_defined=0;
        int ret=sqlite3_exec( db, sql_stmt, tf_callback_if_result_set_empty, &__if_tag_defined, & err_msg);
        if( ret==SQLITE_ABORT || err_msg!=NULL){
            ERR_LOG( err_msg );
            if( err_msg!=NULL)
                sqlite3_free( err_msg);
            err_msg=NULL;
        }
        close_db(db);
        LEAVE_FUNC;
        return __if_tag_defined; 
    }

    LEAVE_FUNC;
    return 0;
}

/**
 * To detect if specified file has been tagged
 * parameters: file
 * return: 0=>false
 *          !0=>true
 */
int if_file_tagged( const char* file){
    ENTER_FUNC;

    if( file==NULL || strlen( file)==0){
        LEAVE_FUNC;
        return 0;
    }

    sqlite3* db=open_db();
    char *err_msg=NULL;
    static char sql_stmt[ DEFAULT_SQL_STMT_BUF_SIZE];

    if( db!=NULL){
        strcpy( sql_stmt, "SELECT * FROM file WHERE file.location=\"");
        strcat( sql_stmt, file);
        strcat( sql_stmt, "\"");

        int __if_file_tagged=0;
        int ret=sqlite3_exec( db, sql_stmt, tf_callback_if_result_set_empty, &__if_file_tagged, &err_msg);
        if( ret==SQLITE_ABORT && err_msg!=NULL){
            ERR_LOG( err_msg);
            if( err_msg!=NULL)
                sqlite3_free( err_msg);
            err_msg=NULL;
            LEAVE_FUNC;
            return 0;
        }
        close_db(db);
        LEAVE_FUNC;
        return __if_file_tagged;
    }

    LEAVE_FUNC;
    return 0;
}

/**
 *  add a tag to the database;
 *  tag: name of tag;
 *  return: 0 OK
 *          !0 Error
 */
int define_tag( const char* tag){
    ENTER_FUNC;
    if( tag==NULL|| strlen( tag)==0){
        LEAVE_FUNC;
        return -1;
    }

    sqlite3* db=open_db();
    static char sql_stmt[DEFAULT_SQL_STMT_BUF_SIZE];
    int ret=0;
    char* err_msg=NULL;

    if( db!=NULL){
        strcpy( sql_stmt, "INSERT INTO tag VALUES ((SELECT max(id) FROM , name)+1, \"");
        strcat( sql_stmt, tag);
        strcat( sql_stmt, "\")");
        ret=sqlite3_exec( db, sql_stmt, NULL, NULL, &err_msg);
        if( ret==SQLITE_ABORT || err_msg!=NULL){
            ERR_LOG( err_msg);
            if( err_msg!=NULL)
                sqlite3_free( err_msg);
            LEAVE_FUNC;
            return -3;
        }else{
            LEAVE_FUNC;
            return 0;
        }
        close_db(db);
    }
    LEAVE_FUNC;
    return -4;
}

/**
 * Determine if specific relationship exist;
 * parameters: relation=> name of the relation;
 * return: 0=>OK, !0=>Error
 */
int if_relation_defined( const char* relation){
    ENTER_FUNC;
    //Todo!
    LEAVE_FUNC;
    return -1;
}

/**
 * Define relationship between tags;
 * parameters: tag1, tag2=> two tags to be linked;
 *      relation=> name of relations;
 */
int if_tags_related( const char* tag1, const char* tag2, 
        const char* relation){
    ENTER_FUNC;
    if( tag1==NULL || tag2==NULL || relation==NULL || 
            strlen( tag1)==0 || strlen(tag2)==0 || strlen(relation)==0){
        LEAVE_FUNC;
        return -1;
    }

    if( if_tag_defined( tag1)){
        define_tag( tag1);
    }
    if( if_tag_defined( tag2)){
        define_tag( tag2);
    }
    if( if_relation_defined( relation)){
        define_relation( relation);
    }
    //Todo !
    LEAVE_FUNC;
}

/**
 * Link certain file with specified tag 
 * parameters: file=> file to be tagged
 *              tag=> tag to be applied to file
 * return: 0 => OK
 *          !0 => Error
 */
int tag_file( const char* file, const char* tag){
    ENTER_FUNC;
    if( file==NULL || tag==NULL || strlen(file)==0 || strlen(tag)==0){
        LEAVE_FUNC;
        return -2;
    }

    sqlite3* db=open_db();
    static char sql_stmt[DEFAULT_SQL_STMT_BUF_SIZE];
    int ret=0;
    char* err_msg=NULL;

    if( ! if_tag_defined(tag))
        define_tag( tag);
    if( ! if_file_tagged( file))
        add_file_record( file);

    if( db!=NULL){
        strcpy( sql_stmt, "INSERT INTO tagfilelink( tid, fid) VALUES ( (SELECT id FROM tag WHERE tag.name= \"");
        strcat( sql_stmt, tag);
        strcat( sql_stmt, "\"),(SELECT id FROM file WHERE file.location=\"");
        strcat( sql_stmt, file);
        strcat( sql_stmt, "\"))");
        ret = sqlite3_exec( db, sql_stmt, NULL, NULL, &err_msg);
        if( ret==SQLITE_ABORT || err_msg!=NULL){
            ERR_LOG( err_msg);
            sqlite3_free( err_msg);
            err_msg=NULL;
        }
        close_db(db);
        LEAVE_FUNC;
        return 0;
    }

    LEAVE_FUNC;
    return -1;
}

/**
 * Remove existing tag;
 * parameters: tag
 * return: 0 => OK
 *          !0 => Error
 */
int undefine_tag( const char* tag){
    ENTER_FUNC;
    if( tag==NULL || strlen(tag)==0){
        LEAVE_FUNC;
        return -1;
    }

    static char sql_stmt[DEFAULT_SQL_STMT_BUF_SIZE];
    char *err_msg;
    sqlite3* db=open_db();

    strcpy( sql_stmt, "DELETE FROM tag WHERE tag.name=\"");
    strcat( sql_stmt, tag);
    strcat( sql_stmt, "\"");

    if( db!=NULL){
        int ret=sqlite3_exec( db, sql_stmt, NULL, NULL, &err_msg);
        if( ret==SQLITE_ABORT || err_msg!=NULL){
            ERR_LOG( err_msg);
            if( err_msg!=NULL)
                sqlite3_free( err_msg);
            err_msg=NULL;
            LEAVE_FUNC;
            return 0;
        }
        close_db(db);
    }
    LEAVE_FUNC;
    return -2;
}

/**
 *  Remove specifc tag away from file;
 *  parameter: file
 *              tag
 *  return: 0 => OK
 *          !0 => Error
 */
int untag_file( const char* file, const char* tag){
    ENTER_FUNC;
    if( file==NULL || tag==NULL || strlen(file)==0 || strlen(tag)==0){
        LEAVE_FUNC;
        return -1;
    }

    static char sql_stmt[DEFAULT_SQL_STMT_BUF_SIZE];
    char* err_msg=NULL;
    sqlite3* db=open_db();

    if( db!=NULL){
        strcpy( sql_stmt, "DELETE FROM tagfilelink WHERE tagfilelink.tid in (SELECT id FROM tag WHERE tag.name = \"");
        strcat( sql_stmt, tag);
        strcat( sql_stmt, "\") AND tagfilelink.fid IN ( SELECT id FROM file WHERE file.location = \"");
        strcat( sql_stmt, file);
        strcat( sql_stmt, "\")");
        int ret=sqlite3_exec( db, sql_stmt, NULL, NULL, &err_msg);
        if( ret==SQLITE_ABORT || err_msg!=NULL){
            ERR_LOG( err_msg);
            if( err_msg!=NULL)
                sqlite3_free( err_msg);
            err_msg=NULL;
        }
        close_db(db);
    }

    LEAVE_FUNC;
    return -2;
}

/**
 *  List tags of a file;
 *  parameters: file=> filename
 *          tags=> pointers to receive result
 *  return: 0 => OK
 *          !0 => Error
 */
// !Todo--- Database must be locked for write operation during the two callbacks which 
//          cona atomic op group

int list_tags_of_file( const char* file, string_list** tags){
    ENTER_FUNC;
    if( file==NULL || strlen(file)==0){
        LEAVE_FUNC;
        return -1;
    }

    if( *tags!=NULL){
        free_string_list( *tags);
        (*tags)=NULL;
    }

    static char sql_stmt[DEFAULT_SQL_STMT_BUF_SIZE];
    char* err_msg;
    int ret=0;
    sqlite3* db=open_db();

    if( db!=NULL){
        (*tags)=alloc_string_list( );

        if( (*tags)!=NULL){
            //Query and fill the result list;
            strcpy( sql_stmt, "SELECT tag.name FROM tag WHERE tag.id IN ( SELECT tagfilelink.tid FROM tagfilelink WHERE tagfilelink.fid IN ( SELECT file.id FROM file WHERE file.location=\"");
            strcat( sql_stmt, file);
            strcat( sql_stmt, "\"))");
            ret=sqlite3_exec( db, sql_stmt, tf_callback_append_string_list, (*tags), &err_msg);
            if( ret==SQLITE_ABORT || err_msg!=NULL){
                ERR_LOG( err_msg);
                if( err_msg!=NULL)
                    sqlite3_free( err_msg);
                err_msg=NULL;
            }
        }
        close_db(db);
        LEAVE_FUNC;
        return 0;
    }

    LEAVE_FUNC;
    return -3;
}

/**
 *  List all the files of specific tag
 *  parameter: tag=> tag name
 *          files=> list of files returned
 *
 */
int list_files_of_tag( const char* tag, string_list** files){
    ENTER_FUNC;
    if( tag==NULL || strlen(tag)==0){
        LEAVE_FUNC;
        return -1;
    }

    if( *files!=NULL){
        free_string_list( *files);
        (*files)=NULL;
    }

    static char sql_stmt[DEFAULT_SQL_STMT_BUF_SIZE];
    char* err_msg;
    int ret=0;
    sqlite3* db=open_db();

    if( db!=NULL){
        (*files)=alloc_string_list( );

        //Query and fill the result list;
        strcpy( sql_stmt, "SELECT file.location FROM file WHERE file.id IN ( SELECT tagfilelink.fid FROM tagfilelink WHERE tagfilelink.tid IN ( SELECT tag.id FROM tag WHERE tag.name=\"");
        strcat( sql_stmt, tag);
        strcat( sql_stmt, "\"))");
        ret = sqlite3_exec( db, sql_stmt, tf_callback_append_string_list, (*files), &err_msg);
        if( ret==SQLITE_ABORT || err_msg!=NULL){
            ERR_LOG( err_msg);
            if( err_msg!=NULL)
                sqlite3_free( err_msg);
            err_msg=NULL;
        }
        close_db(db);
        LEAVE_FUNC;
        return 0;
    }

    LEAVE_FUNC;
    return -3;
}

/**
 * List all available tags, will overwrite result set;
 * parameters: tags=> result holder
 * return: 0=>OK
 *         !0=>Error
 */
int list_all_tags( string_list** tags){
    ENTER_FUNC;
    if( (*tags)!=NULL){
        free_string_list( *tags);
    }

    char *err_msg=NULL;
    int ret=0;
    sqlite3* db=open_db();

    if( db!=NULL){
        (*tags)=alloc_string_list();
        ret=sqlite3_exec( db, "SELECT tag.name FROM tag", tf_callback_append_string_list, (*tags), &err_msg);
        if( ret==SQLITE_ABORT || err_msg!=NULL){
            ERR_LOG( err_msg);
            if( err_msg!=NULL){
                sqlite3_free( err_msg);
                err_msg=NULL;
            }
        }
        close_db(db);
        LEAVE_FUNC;
        return 0;
    }
    LEAVE_FUNC;
    return -1;
}

/**
 * List all the tagged files;
 * parameters: files=> result set, will be overwrited if not NULL
 * return: 0=> OK,
 *          !0=>Error
 */
int list_all_files( string_list** files){
    ENTER_FUNC;
    if( (*files)!=NULL){
        free_string_list( *files);
    }
    sqlite3* db=open_db();
    int ret=0;
    char *err_msg;

    if( db!=NULL){
        (*files)=alloc_string_list();
        
        ret=sqlite3_exec( db, "SELECT file.location FROM file", tf_callback_append_string_list, (*files), &err_msg);
        if( ret==SQLITE_ABORT || NULL!=err_msg){
            ERR_LOG( err_msg);
            if( NULL!=err_msg){
                sqlite3_free(err_msg);
            }
            LEAVE_FUNC;
            return -3;
        }
        close_db(db);
    }

    LEAVE_FUNC;
    return -2;
}

/**
 * Private function to add a filename to database;
 * parameters: filename
 * return: 0 => OK
 *          !0 => Error
 */
static int add_file_record(const char* filename){
    ENTER_FUNC;
    if( filename==NULL || strlen(filename)==0){
        LEAVE_FUNC;
        return -1;
    }

    sqlite3* db=open_db();
    static char sql_stmt[DEFAULT_SQL_STMT_BUF_SIZE];
    int ret=0;
    char* err_msg=NULL;


    if( db!=NULL){
        strcpy( sql_stmt, "INSERT INTO file VALUES ( (SELECT max(id) FROM file)+1,");
        strcat( sql_stmt, filename);
        strcat( sql_stmt, "\",0)");
        ret=sqlite3_exec( db, sql_stmt, NULL, NULL, &err_msg);
        if( ret==SQLITE_ABORT || NULL!=err_msg){
            ERR_LOG( err_msg);
            if( err_msg!=NULL)
                sqlite3_free( err_msg);
            err_msg=NULL;
            LEAVE_FUNC;
            return -3;
        }else{
            LEAVE_FUNC;
            return 0;
        }
        close_db(db);
    }

    LEAVE_FUNC;
    return -4;
}

/**
 * Define a relationship between tags
 * parameter: relation=> name of relationship
 * return: 0=>OK, !0=>Error
 */
int define_relation( const char* relation){
    ENTER_FUNC;
    // Todo!--
    LEAVE_FUNC;
    return -1;
}

#if defined( __cplusplus)
}   // extern "C"{
#endif
